﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using ArkanoidX.Objects;

namespace ArkanoidX.UI
{
    public class Window
    {
        public enum ModalResult
        {
            OK,
            Cancel
        }

        public enum State
        {
            Created,
            Initialized,
            Running,
            Inactive,
            Closing,
            Closed
        }

        public enum AlignType
        {
            TopLeft,
            TopCenter,
            TopRight,
            LeftCenter,
            Center,
            RightCenter,
            BottomLeft,
            BottomCenter,
            BottomRight,
        }

        public class WindowOperation
        {
            public Window parent;
            public Window window;
            public enum OpType
            {
                Add,
                Remove,
                SetActive,
                SetInactive,
                SetFocused,
                SetUnfocused,
            }
            public OpType operType;
        }

        public class Event
        {
            public bool consumed = false;
            public bool needfocus = true;
        }

        public class KeyStrokeEvent : Event
        {
            public Keys key;
            public KeyStrokeEvent(Keys key)
            {
                this.key = key;
            }
        }


        public Window parent = null;
        public List<Window> children = new List<Window>();
        public List<WindowOperation> childOpers = new List<WindowOperation>();
        public Queue<WindowOperation> coQueue = new Queue<WindowOperation>();

        protected Rectangle boundingRect;
        public State state;
        protected ModalResult modalResult;
        protected Texture2D background;
        protected bool isDialog = false;
        public Color DrawColor = Color.White;
        public Color FontColor = Color.White;


        public bool canBeActivated = true;
        public bool canBeFocused = false;

        public bool focused = false;

        public MainGame Game;

        public Window(MainGame game)
            : this(game, null)
        {
        }

        public Window(MainGame game, Window parent)
            : this(game, parent, false)
        {
        }

        public Window GetRoot()
        {
            Window root = this;
            while (root.parent != null)
            {
                root = root.parent;
            }
            return root;
        }


        public Window(MainGame game, Window parent, bool isRoot)
        {
            Game = game;
            if (isRoot)
            {
                state = State.Running;
                return;
            }
            background = game.Content.Load<Texture2D>("black");
            if (parent == null)
            {
                parent = game.RootWindow;
            }
            this.parent = parent;
            state = State.Created;
        }

        public virtual void SetRectangle(Rectangle rect)
        {
            boundingRect = rect;
        }

        public virtual Rectangle GetRectangle()
        {
            return boundingRect;
        }

        public void AddChild(Window child)
        {
            child.parent = this;
            child.AddToGame();
            if (this.focused)
            {
                this.Focus();    // propagate to children
            }
        }

        protected void DoSetInactive()
        {
            if (this.state == State.Running)
            {
                this.state = State.Inactive;
            }
        }

        protected void DoSetActive()
        {
            if ((this.state == State.Inactive || this.state == State.Created)
                && this.canBeActivated)
            {
                this.state = State.Running;
            }

            // activate all children
            foreach (Window child in children)
            {
                child.SetActive();
            }
        }

        protected void DoSetFocused()
        {
            if (this.canBeFocused && this.children.Count == 0)
                this.focused = true;

            // make sure at least one child is set focus
            bool bOneFocused = false;
            foreach (Window child in children)
            {
                if (child.focused)
                    bOneFocused = true;
            }
            if (!bOneFocused && children.Count > 0)
            {
                foreach (Window child in children)
                {
                    if (child.canBeFocused)
                    {
                        child.Focus();
                    }
                }
            }

            if (!bOneFocused)
            {
                this.focused = true;
            }

            // unfocus all others
            if (this.focused)
                Game.RootWindow.UnfocusAllExcept(this);
        }

        protected void UnfocusAllExcept(Window except)
        {
            if (this != except && this.focused)
                this.focused = false;

            foreach (Window child in children)
            {
                child.UnfocusAllExcept(except);
            }
        }

        public void CloseAllChildren()
        {
            foreach (Window child in children)
            {
                child.Close();
            }
        }

        protected bool TryFocusExcept(Window w)
        {
            if (this.canBeFocused && this.children.Count == 0 && this != w)
                this.focused = true;

            // make sure at least one child is set focus
            bool bOneFocused = false;
            foreach (Window child in children)
            {
                if (child.focused)
                    bOneFocused = true;
            }
            if (!bOneFocused && children.Count > 0)
            {
                foreach (Window child in children)
                {
                    if (child.canBeFocused)
                    {
                        if (child.TryFocusExcept(w))
                            return true;
                    }
                }
            }

            if (!bOneFocused && this != w)
            {
                this.focused = true;
            }

            return this.focused;
        }

        protected void DoSetUnfocused()
        {
            if (!this.focused)
                return;
            if (this.parent == null)
                return; // impossible to be here

            bool oneFocused = false;
            foreach (Window sibling in this.parent.children)
            {
                if (sibling == this) continue;
                if (sibling.canBeFocused && !sibling.focused)
                {
                    sibling.Focus();
                    oneFocused = true;
                    break;
                }
            }
            Window p = this.parent;
            while (!oneFocused && p != null)
            {
                oneFocused = p.TryFocusExcept(this);
            }
        }

        public virtual ModalResult GetModalResult()
        {
            return modalResult;
        }

        public void AddToGame()
        {
            if (parent == null)
            {
                parent = this.GetRoot();
            }
            GetRoot().coQueue.Enqueue(new WindowOperation()
            {
                parent = this.parent,
                window = this,
                operType = WindowOperation.OpType.Add
            });
        }

        public void RemoveFromGame()
        {
            if (parent == null)
            {
                parent = this.GetRoot();
            }
            GetRoot().coQueue.Enqueue(new WindowOperation()
            {
                parent = this.parent,
                window = this,
                operType = WindowOperation.OpType.Remove
            });

        }

        public virtual void Show()
        {
            this.AddToGame();
            this.SetActive();

            Focus();
        }

        public void Focus()
        {
            if (parent == null)
            {
                parent = this.GetRoot();
            }
            GetRoot().coQueue.Enqueue(new WindowOperation()
            {
                parent = this.parent,
                window = this,
                operType = WindowOperation.OpType.SetFocused
            });
        }

        public void UnFocus()
        {
            if (parent == null)
            {
                parent = this.GetRoot();
            }
            GetRoot().coQueue.Enqueue(new WindowOperation()
            {
                parent = this.parent,
                window = this,
                operType = WindowOperation.OpType.SetUnfocused
            });
        }

        public void SetActive()
        {
            if (!canBeActivated)
            {
                return;
            }
            if (parent != null)
            {
                GetRoot().coQueue.Enqueue(new WindowOperation()
                {
                    parent = this.parent,
                    window = this,
                    operType = WindowOperation.OpType.SetActive
                }); 
            }
        }

        public virtual void Close()
        {
            this.Destroy();
            this.state = State.Closed;
        }

        protected virtual void Destroy()
        {
            foreach (Window child in children)
            {
                child.RemoveFromGame();
            }

            this.RemoveFromGame();
            this.UnFocus();
        }

        public void UpdateAsRoot(GameTime gameTime)
        {
            while (coQueue.Count > 0)
            {
                while (coQueue.Count > 0)
                {
                    WindowOperation wo = coQueue.Dequeue();
                    childOpers.Add(wo);
                }

                foreach (WindowOperation wo in this.childOpers)
                {
                    switch (wo.operType)
                    {
                        case WindowOperation.OpType.Add:
                            {
                                Window window = wo.window;
                                Window r = children.Find(new Predicate<Window>((w) => { return window == w; }));
                                if (r == window)
                                    continue;
                                wo.parent.children.Add(window);
                            }
                            break;
                        case WindowOperation.OpType.Remove:
                            {
                                wo.parent.children.Remove(wo.window);
                            }
                            break;
                        case WindowOperation.OpType.SetFocused:
                            {
                                wo.window.DoSetFocused();
                            }
                            break;
                        case WindowOperation.OpType.SetUnfocused:
                            {
                                wo.window.DoSetUnfocused();
                            }
                            break;
                        case WindowOperation.OpType.SetActive:
                            {
                                wo.window.DoSetActive();
                            }
                            break;
                        case WindowOperation.OpType.SetInactive:
                            {
                                wo.window.DoSetInactive();
                            }
                            break;
                    }
                }

                this.childOpers.Clear();
            }

            // detect key events
            KeyboardState kbs = Keyboard.GetState();
            KeyStrokeEvent kse = null;
            if (!Game.lkbsUI.LastBackPressed && kbs.IsKeyDown(Keys.Back))  // back
            {
                kse = new KeyStrokeEvent(Keys.Back);
            }
            else if (!Game.lkbsUI.LastTabPressed && kbs.IsKeyDown(Keys.Tab))   // tab
            {
                kse = new KeyStrokeEvent(Keys.Tab);
            }
            else if (!Game.lkbsUI.LastEnterPressed && kbs.IsKeyDown(Keys.Enter))   // enter
            {
                kse = new KeyStrokeEvent(Keys.Enter);
            }
            else if (!Game.lkbsUI.LastEscPressed && kbs.IsKeyDown(Keys.Escape)) //esc
            {
                kse = new KeyStrokeEvent(Keys.Escape);
            }
            else if (!Game.lkbsUI.LastUpPressed && kbs.IsKeyDown(Keys.Up)) // up
            {
                kse = new KeyStrokeEvent(Keys.Up);
            }
            else if (!Game.lkbsUI.LastDownPressed && kbs.IsKeyDown(Keys.Down)) // down
            {
                kse = new KeyStrokeEvent(Keys.Down);
            }
            else
            {
                for (int i = 0; i < 26; i++)
                {
                    Keys key = Keys.A + i;
                    if (!Game.lkbsUI.LastLetterPressed[i] && kbs.IsKeyDown(key))
                    {
                        kse = new KeyStrokeEvent(key);
                    }
                }
                for (int i = 0; i < 10; i++)
                {
                    Keys key = Keys.D0 + i;
                    if (!Game.lkbsUI.LastDigitPressed[i] && kbs.IsKeyDown(key))
                    {
                        kse = new KeyStrokeEvent(key);
                    }
                }
            }

            if (kse != null)
            {
                PassOnEvent(gameTime, kse);
            }

            foreach (Window child in this.children)
            {
                child.Update(gameTime);
            }
        }

        public void PassOnEvent(GameTime gameTime, Event e)
        {
            foreach (Window child in this.children)
            {
                if (child.state == State.Running)
                {
                    child.PassOnEvent(gameTime, e);
                }
            }
            if (!e.consumed && parent != null)
            {
                if (!e.needfocus || this.focused)
                    TryHandleEvent(gameTime, e);
            }
        }

        public virtual void TryHandleEvent(GameTime gameTime, Event e)
        {
            if (e is KeyStrokeEvent)
            {
                TryHandleKeyEvent(gameTime, (KeyStrokeEvent)e);
            }
        }
        public virtual void TryHandleKeyEvent(GameTime gameTime, KeyStrokeEvent e)
        {
        }

        public virtual void Update(GameTime gameTime)
        {
            if (this.parent == null)
            {
                UpdateAsRoot(gameTime);
                return;
            }

            if (state == State.Closing)
            {
                this.Destroy();
            }

            foreach (Window child in this.children)
            {
                child.Update(gameTime);
            }
        }

        public virtual void Draw(GameTime gameTime)
        {
            SpriteBatch sBatch = (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));

            if (background != null)
            {
                sBatch.Draw(background, this.boundingRect, DrawColor);
            }

            foreach (Window child in this.children)
            {
                child.Draw(gameTime);
            }
        }

        public virtual void Align(Rectangle rect, AlignType type, Vector2 size, float xMargin, float yMargin)
        {
            Vector2 halfSize = size * 0.5f;
            Vector2 pos = CalcAlign(rect, type, size, xMargin, yMargin);
            int left = (int)(pos - halfSize).X;
            int top = (int)(pos - halfSize).Y;
            int width = (int)size.X;
            int height = (int)size.Y;
            this.SetRectangle(new Rectangle(left, top, width, height));
        }

        public Vector2 CalcAlign(Rectangle rect, AlignType type, Vector2 size, float xMargin, float yMargin)
        {
            Vector2 halfSize = size * 0.5f;

            float xLeft = rect.Left + xMargin + halfSize.X;
            float xCenter = rect.Left + rect.Width * 0.5f;
            float xRight = rect.Right - halfSize.X - xMargin;

            float yTop = rect.Top + yMargin + halfSize.Y;
            float yCenter = rect.Top + rect.Height * 0.5f;
            float yBottom = rect.Bottom - halfSize.Y - yMargin;

            switch (type)
            {
                case AlignType.TopLeft:
                    return new Vector2(xLeft, yTop);
                case AlignType.TopCenter:
                    return new Vector2(xCenter, yTop);
                case AlignType.TopRight:
                    return new Vector2(xRight, yTop);
                case AlignType.LeftCenter:
                    return new Vector2(xLeft, yCenter);
                case AlignType.Center:
                    return new Vector2(xCenter, yCenter);
                case AlignType.RightCenter:
                    return new Vector2(xRight, yCenter);
                case AlignType.BottomLeft:
                    return new Vector2(xLeft, yBottom);
                case AlignType.BottomCenter:
                    return new Vector2(xCenter, yBottom);
                case AlignType.BottomRight:
                    return new Vector2(xRight, yBottom);
                default:
                    throw new NotImplementedException();
            }
        }
    }
}
